package com.demo.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.demo.entity.StudentEntity;
import com.demo.entity.TeacherEntity;
import com.demo.mapper.StudentMapper;
import com.demo.mapper.TeacherMapper;
import com.demo.token.Token;
import com.demo.token.TokenModel;
import com.demo.util.RSAEncrypt;
import com.demo.util.Redis;
import com.demo.util.ReturnErrorCode;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import sun.misc.BASE64Encoder;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;

@RestController
public class LoginController {

    Log log = LogFactory.getLog("三岁小仙仙");

    Jedis redis = Redis.getRedis();

    @Resource
    TeacherMapper teacherMapper;

    @Resource
    StudentMapper studentMapper;

    @Resource
    Token token;

    /**
     * 登录页获取公钥
     *
     * @return
     */
    @GetMapping("/getPubKey")
    private String getPubKey() {
        String pubKey = RSAEncrypt.getPubKey();
        return pubKey;
    }

    /**
     * 教师/学生登录
     *
     * @param userid   教师账号
     * @param userpass 教师密码
     * @param code     验证码
     * @return -1：验证码过期
     * 0：验证码错误
     * 1：用户名或密码错误
     * 2：登录成功
     */
    @PostMapping("/login")
    public String[] login(HttpServletResponse response, String userid, String userclass, String userpass, boolean type, String code) {
        JSONObject jsonObject = JSONObject.parseObject(code);
        String userCode = jsonObject.getString("code");
        String systemCode = jsonObject.getString("_c");
        if (!checkLoginStatusIsLock(userid)) {
            String decrypt = RSAEncrypt.decrypt(systemCode, RSAEncrypt.getPrvKey());
            if (redis.exists(userCode)) redis.del(userCode);
            else if (!systemCode.equals("") && redis.exists(decrypt)) {
                redis.del(decrypt);
                return new String[]{"0"};
            } // 验证码错误，账号已锁定的信息不用返回
            else return new String[]{"-1"};//验证码已过期
            new ReturnErrorCode(response, 402);
            return null;
        }

        try{
            //学生验证通道
            if (redis.exists(userCode) && !type) {
                redis.del(userCode);
                return studentLogin(userid, userclass, userpass);
            }
            //教师验证通道
            if (redis.exists(userCode) && type) {
                redis.del(userCode);
                return teacherLogin(userid, userpass);
            }
        }catch (Exception e){return new String[]{"-2"};}
        //教师学生验证失败，删除验证码
        if (!systemCode.equals("")) {
            String decrypt = RSAEncrypt.decrypt(systemCode, RSAEncrypt.getPrvKey());
            redis.del(decrypt);
            return new String[]{"0"}; // 验证码错误
        }
        return new String[]{"-1"}; // 验证码过期

    }

    /**
     * 检查用户登录状态是否已经锁定
     *
     * @param userID 用户编号
     * @return true/false
     */
    private boolean checkLoginStatusIsLock(String userID) {
        String prefix = "login_status_lock_" + userID.substring(0, 7), key = userID.substring(7);
        if (!redis.exists(prefix)) return true;
        if (!redis.hexists(prefix, key)) return true;
        String hget = redis.hget(prefix, key);
        try {
            JSONObject jsonObject = JSON.parseObject(hget);
            String lockTime = jsonObject.getString("lockTime");
            long result = (Long.valueOf(lockTime) - System.currentTimeMillis()) / 1000 / 60 / 60;
            if (result <= 0) redis.hdel(prefix, key);
        } catch (JSONException j) {
            return true;
        }
        return false;
    }

    /**
     * 生成验证码
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @PostMapping("/getCode")
    private synchronized String getCode(String val, HttpServletRequest request, HttpServletResponse response) throws IOException {
        //创建图片缓冲区
        BufferedImage bi = new BufferedImage(100, 30, BufferedImage.TYPE_3BYTE_BGR);
        //在缓冲区上创建画布
        Graphics gh = bi.getGraphics();
        //设置画布背景颜色
        gh.setColor(Color.orange);
        //创建画布矩形
        gh.fillRect(0, 0, 100, 30);
        //创建随机数
        //存储随机数
        SecureRandom random = null;
        try {
            random = SecureRandom.getInstance("SHA1PRNG");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        int number = 48, capitalLetter = 65, lowercaseLetters = 97;
        Set<Character> set = new HashSet<>();
        do {
            set.clear();
            set.add((char) (number + random.nextInt(10)));
            set.add((char) (capitalLetter + random.nextInt(10)));
            set.add((char) (number + random.nextInt(10)));
            set.add((char) (lowercaseLetters + random.nextInt(10)));
        } while (set.size() != 4);

        Iterator<Character> iterator = set.iterator();
        int count = 0;
        while (iterator.hasNext()) {
            //设置随机颜色
            gh.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            //设置字体类型
            gh.setFont(new Font("", Font.BOLD, 20));
            //将随机数画在画布上
            gh.drawString(String.valueOf(iterator.next()), (count++) * 20 + 4, 20);
        }

        int row = 10, cls = 4, canvasWidth = 100, canvasHeight = 30;
        int array[][] = new int[row][cls];
        for (int i = 0; i < array.length; i++) {
            array[i][0] = (int) Math.round(Math.random() * canvasWidth);
            array[i][1] = (int) Math.round(Math.random() * canvasHeight);
            // 以上两个[0][1]为第一个坐标点
            array[i][2] = (int) Math.round(Math.random() * canvasWidth);
            array[i][3] = (int) Math.round(Math.random() * canvasHeight);
            // 以上两个[2][3]为第二个坐标点，判断两个坐标不能相等
            if (array[i][0] == array[i][2] && array[i][1] == array[i][3]) {
                i--;
                continue;
            }
        }
        Graphics2D g2 = (Graphics2D) gh;  //g是Graphics对象
        for (int i = 0; i < array.length; i++) {
            //设置随机颜色
            g2.setStroke(new BasicStroke(1.0f));
            g2.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            g2.drawLine(array[i][0], array[i][1], array[i][2], array[i][3]);
        }

        String code = "";
        iterator = set.iterator();
        while (iterator.hasNext()) {
            code += iterator.next();
        }
        redis.set(code, code);
        redis.expire(code, 300);
        // 页面上直接返回图片
        //ImageIO.write(bi, "jpg", response.getOutputStream());

        // 返回Base64编码的图片
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(bi, "jpg", outputStream);
        BASE64Encoder base64Encoder = new BASE64Encoder();
        String base64 = base64Encoder.encode(outputStream.toByteArray()).trim();
        base64.replaceAll("\n", "").replaceAll("\r", "");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "data:image/jpg;base64," + base64);

        // 加密验证码
        String encrypt = RSAEncrypt.encrypt(code, RSAEncrypt.getPubKey());
        jsonObject.put("_c", encrypt);
        // 解密上次获取的验证码 并删除
        if (val != "" && val != null) {
            String decrypt = RSAEncrypt.decrypt(val, RSAEncrypt.getPrvKey());
            redis.del(decrypt);
        }
        gh.dispose();
        return jsonObject.toString();
    }

    /**
     * 教师登录调用
     *
     * @param userid   教师工号
     * @param userpass 教师密码
     * @return 1（用户账号或密码错误） 或 2（登录成功）
     */
    private String[] teacherLogin(String userid, String userpass) {
        //========================登录失败状态锁定数据区==========================
        Map<String, String> userInfo = new HashMap<>();
        String prefix = userid.substring(0, 7);
        userInfo.put(userid.substring(7), "1");
        //========================登录失败状态锁定数据区==========================
        try {
            TeacherEntity byId = teacherMapper.findById(userid);
            if (byId != null) {
                // 若为初始密码，需要进行md5, 再后和私钥解密后的密码匹配
                String teacherPass = byId.getTeacherPass().equals("123456") ? DigestUtils.md5Hex(byId.getTeacherPass()) : byId.getTeacherPass();
                String decrypt = RSAEncrypt.decrypt(userpass, RSAEncrypt.getPrvKey());
                if (teacherPass.equals(decrypt)) {
                    TokenModel tokenModel = token.create(userid, "true", 60 * 3);
                    if (!byId.getTeacherPass().equals("123456")) {
                        tokenModel = token.create(userid);
                    }
                    loginStatusUnLock(prefix, userInfo.keySet().iterator().next());
                    return new String[]{"2", byId.getTeacherName(), JSON.toJSONString(tokenModel)};
                }
            }
        } catch (Exception e) {
            loginStatusLock(prefix, userInfo);
            return new String[]{"1"};
        }
        loginStatusLock(prefix, userInfo);
        return new String[]{"1"};
    }

    /**
     * 学生登录调用
     *
     * @param userid    学生账号
     * @param userclass 学生班级号
     * @return 1或2
     */
    private String[] studentLogin(String userid, String userclass, String userpass) {
        Map<String, String> map = new HashMap<>();
        map.put("classNumber", userclass);
        map.put("userid", userid);
        StudentEntity classStudent = null;
        //========================登录失败状态锁定数据区==========================
        Map<String, String> userInfo = new HashMap<>();
        String prefix = userid.substring(0, 7);
        userInfo.put(userid.substring(7), "1");
        //========================登录失败状态锁定数据区==========================
        try {
            classStudent = studentMapper.findClassStudent(map);
        } catch (Exception e) {
            loginStatusLock(prefix, userInfo);
            return new String[]{"1"};
        }
        if (classStudent == null) {
            loginStatusLock(prefix, userInfo);
            return new String[]{"1"}; // 用户名或密码错误
        }
        String studentPass = null;
        if (classStudent != null) {
            studentPass = classStudent.getStudentPass().equals("123456") ? DigestUtils.md5Hex(classStudent.getStudentPass()) : classStudent.getStudentPass();
        }
        String decrypt = RSAEncrypt.decrypt(userpass, RSAEncrypt.getPrvKey());
        if (studentPass.equals(decrypt)) {
            TokenModel tokenModel = token.create(userid, "true", 60 * 3);
            if (!classStudent.getStudentPass().equals("123456")) {
                tokenModel = token.create(userid);
            }
            loginStatusUnLock(prefix, userInfo.keySet().iterator().next());
            return new String[]{"2", classStudent.getStudentName(), JSON.toJSONString(tokenModel)};//登录成功
        }
        loginStatusLock(prefix, userInfo);
        return new String[]{"1"};
    }

    /**
     * 登录状态锁定
     *
     * @param prefix 前缀（班级区分）
     * @param map    编号/次数（key/value）
     * @return true/false
     */
    private boolean loginStatusLock(String prefix, Map map) {
        prefix = "login_status_lock_" + prefix;
        String key = map.keySet().iterator().next().toString();
        if (!redis.exists(prefix) || !redis.hexists(prefix, key)) {
            redis.hmset(prefix, map);
            return true;
        } else {
            Integer status = Integer.valueOf(redis.hget(prefix, key));
            if (status++ < 3) {
                map.put(key, String.valueOf(status));
                if (status == 3) {
                    //获取日期时间
                    Calendar calendar = Calendar.getInstance();
                    //设置日期时间到明天此刻
                    calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) + 1);
                    //获取日期时间毫秒
                    long timeInMillis = calendar.getTimeInMillis();
                    //封装用户状态、用户锁定时间
                    Map<String, String> lockTime = new HashMap<>();
                    lockTime.put("status", String.valueOf(status));
                    lockTime.put("lockTime", String.valueOf(timeInMillis));
                    //将封装信息添加hash中
                    map.put(key, JSON.toJSONString(lockTime));
                    //添加信息添加到存储到redis中
                }
                redis.hmset(prefix, map);
                return true;
            }

        }
        return false;
    }

    /**
     * 登录状态解锁
     *
     * @param prefix 前缀
     * @param key    关键字
     */
    private void loginStatusUnLock(String prefix, String key) {
        prefix = "login_status_lock_" + prefix;
        if (redis.exists(prefix) && redis.hexists(prefix, key)) redis.hdel(prefix, key);
    }

    /**
     * 学生/教师修改密码通道
     *
     * @param obj json数据主体
     * @return true/false
     */
    @PostMapping("/changePass")
    public boolean changePass(String obj) {
        JSONObject jsonObject = JSON.parseObject(obj);
        String priKey = RSAEncrypt.getPrvKey();
        String stuNumber = RSAEncrypt.decrypt(jsonObject.getString("stuNumber"), priKey);
        String stuClass = RSAEncrypt.decrypt(jsonObject.getString("stuClass"), priKey);
        String oldPass = RSAEncrypt.decrypt(jsonObject.getString("oldPass"), priKey);
        try {
            if (!stuClass.equals("teacher")) {
                //学生修改密码通道
                Map<String, String> map = new HashMap<>();
                map.put("classNumber", stuClass);
                map.put("userid", stuNumber);
                StudentEntity classStudent = studentMapper.findClassStudent(map);
                if (classStudent == null) return false;
                if (!classStudent.getStudentName().equals(jsonObject.getString("stuName"))) return false;
                String stuPass = classStudent.getStudentPass();
                if (stuPass.equals("123456")) stuPass = DigestUtils.md5Hex(stuPass);
                if (!stuPass.equals(oldPass)) return false;
                String newPass = RSAEncrypt.decrypt(jsonObject.getString("newPass"), priKey);
                boolean updateStudentPass = studentMapper.updateStudentPass(stuClass, stuNumber, newPass);
                if (updateStudentPass) return true;
            } else {
                //教师修改密码通道
                TeacherEntity teacherEntity = teacherMapper.findById(stuNumber);
                if (teacherEntity == null) return false;
                if (!teacherEntity.getTeacherName().equals(jsonObject.getString("stuName"))) return false;
                String teaPass = teacherEntity.getTeacherPass();
                if (teaPass.equals("123456")) teaPass = DigestUtils.md5Hex(teaPass);
                if (!teaPass.equals(oldPass)) return false;
                String newPass = RSAEncrypt.decrypt(jsonObject.getString("newPass"), priKey);
                boolean updateTeacherPass = teacherMapper.updateTeacherPass(stuNumber, newPass);
                if (updateTeacherPass) return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    @GetMapping("/logOut")
    public void logOut(String userNumber) {
        redis.del(userNumber);
    }
}
